1   /*
2    * Copyright (c) 2000, 2001, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  package com.sun.imageio.plugins.png;
27  
28  import java.awt.image.ColorModel;
29  import java.awt.image.IndexColorModel;
30  import java.awt.image.SampleModel;
31  import java.util.ArrayList;
32  import java.util.Iterator;
33  import java.util.StringTokenizer;
34  import javax.imageio.ImageTypeSpecifier;
35  import javax.imageio.metadata.IIOInvalidTreeException;
36  import javax.imageio.metadata.IIOMetadata;
37  import javax.imageio.metadata.IIOMetadataFormat;
38  import javax.imageio.metadata.IIOMetadataFormatImpl;
39  import javax.imageio.metadata.IIOMetadataNode;
40  import org.w3c.dom.Node;
41  
42  public class PNGMetadata extends IIOMetadata implements Cloneable {
43  
44      // package scope
45      public static final String
46          nativeMetadataFormatName = "javax_imageio_png_1.0";
47  
48      protected static final String nativeMetadataFormatClassName
49          = "com.sun.imageio.plugins.png.PNGMetadataFormat";
50  
51      // Color types for IHDR chunk
52      public static final String[] IHDR_colorTypeNames = {
53          "Grayscale", null, "RGB", "Palette",
54          "GrayAlpha", null, "RGBAlpha"
55      };
56  
57      public static final int[] IHDR_numChannels = {
58          1, 0, 3, 3, 2, 0, 4
59      };
60  
61      // Bit depths for IHDR chunk
62      public static final String[] IHDR_bitDepths = {
63          "1", "2", "4", "8", "16"
64      };
65  
66      // Compression methods for IHDR chunk
67      public static final String[] IHDR_compressionMethodNames = {
68          "deflate"
69      };
70  
71      // Filter methods for IHDR chunk
72      public static final String[] IHDR_filterMethodNames = {
73          "adaptive"
74      };
75  
76      // Interlace methods for IHDR chunk
77      public static final String[] IHDR_interlaceMethodNames = {
78          "none", "adam7"
79      };
80  
81      // Compression methods for iCCP chunk
82      public static final String[] iCCP_compressionMethodNames = {
83          "deflate"
84      };
85  
86      // Compression methods for zTXt chunk
87      public static final String[] zTXt_compressionMethodNames = {
88          "deflate"
89      };
90  
91      // "Unknown" unit for pHYs chunk
92      public static final int PHYS_UNIT_UNKNOWN = 0;
93  
94      // "Meter" unit for pHYs chunk
95      public static final int PHYS_UNIT_METER = 1;
96  
97      // Unit specifiers for pHYs chunk
98      public static final String[] unitSpecifierNames = {
99          "unknown", "meter"
100     };
101 
102     // Rendering intents for sRGB chunk
103     public static final String[] renderingIntentNames = {
104         "Perceptual", // 0
105         "Relative colorimetric", // 1
106         "Saturation", // 2
107         "Absolute colorimetric" // 3
108 
109     };
110 
111     // Color space types for Chroma->ColorSpaceType node
112     public static final String[] colorSpaceTypeNames = {
113         "GRAY", null, "RGB", "RGB",
114         "GRAY", null, "RGB"
115     };
116 
117     // IHDR chunk
118     public boolean IHDR_present;
119     public int IHDR_width;
120     public int IHDR_height;
121     public int IHDR_bitDepth;
122     public int IHDR_colorType;
123     public int IHDR_compressionMethod;
124     public int IHDR_filterMethod;
125     public int IHDR_interlaceMethod; // 0 == none, 1 == adam7
126 
127     // PLTE chunk
128     public boolean PLTE_present;
129     public byte[] PLTE_red;
130     public byte[] PLTE_green;
131     public byte[] PLTE_blue;
132 
133     // If non-null, used to reorder palette entries during encoding in
134     // order to minimize the size of the tRNS chunk.  Thus an index of
135     // 'i' in the source should be encoded as index 'PLTE_order[i]'.
136     // PLTE_order will be null unless 'initialize' is called with an
137     // IndexColorModel image type.
138     public int[] PLTE_order = null;
139 
140     // bKGD chunk
141     // If external (non-PNG sourced) data has red = green = blue,
142     // always store it as gray and promote when writing
143     public boolean bKGD_present;
144     public int bKGD_colorType; // PNG_COLOR_GRAY, _RGB, or _PALETTE
145     public int bKGD_index;
146     public int bKGD_gray;
147     public int bKGD_red;
148     public int bKGD_green;
149     public int bKGD_blue;
150 
151     // cHRM chunk
152     public boolean cHRM_present;
153     public int cHRM_whitePointX;
154     public int cHRM_whitePointY;
155     public int cHRM_redX;
156     public int cHRM_redY;
157     public int cHRM_greenX;
158     public int cHRM_greenY;
159     public int cHRM_blueX;
160     public int cHRM_blueY;
161 
162     // gAMA chunk
163     public boolean gAMA_present;
164     public int gAMA_gamma;
165 
166     // hIST chunk
167     public boolean hIST_present;
168     public char[] hIST_histogram;
169 
170     // iCCP chunk
171     public boolean iCCP_present;
172     public String iCCP_profileName;
173     public int iCCP_compressionMethod;
174     public byte[] iCCP_compressedProfile;
175 
176     // iTXt chunk
177     public ArrayList<String> iTXt_keyword = new ArrayList<String>();
178     public ArrayList<Boolean> iTXt_compressionFlag = new ArrayList<Boolean>();
179     public ArrayList<Integer> iTXt_compressionMethod = new ArrayList<Integer>();
180     public ArrayList<String> iTXt_languageTag = new ArrayList<String>();
181     public ArrayList<String> iTXt_translatedKeyword = new ArrayList<String>();
182     public ArrayList<String> iTXt_text = new ArrayList<String>();
183 
184     // pHYs chunk
185     public boolean pHYs_present;
186     public int pHYs_pixelsPerUnitXAxis;
187     public int pHYs_pixelsPerUnitYAxis;
188     public int pHYs_unitSpecifier; // 0 == unknown, 1 == meter
189 
190     // sBIT chunk
191     public boolean sBIT_present;
192     public int sBIT_colorType; // PNG_COLOR_GRAY, _GRAY_ALPHA, _RGB, _RGB_ALPHA
193     public int sBIT_grayBits;
194     public int sBIT_redBits;
195     public int sBIT_greenBits;
196     public int sBIT_blueBits;
197     public int sBIT_alphaBits;
198 
199     // sPLT chunk
200     public boolean sPLT_present;
201     public String sPLT_paletteName; // 1-79 characters
202     public int sPLT_sampleDepth; // 8 or 16
203     public int[] sPLT_red;
204     public int[] sPLT_green;
205     public int[] sPLT_blue;
206     public int[] sPLT_alpha;
207     public int[] sPLT_frequency;
208 
209     // sRGB chunk
210     public boolean sRGB_present;
211     public int sRGB_renderingIntent;
212 
213     // tEXt chunk
214     public ArrayList<String> tEXt_keyword = new ArrayList<String>(); // 1-79 characters
215     public ArrayList<String> tEXt_text = new ArrayList<String>();
216 
217     // tIME chunk
218     public boolean tIME_present;
219     public int tIME_year;
220     public int tIME_month;
221     public int tIME_day;
222     public int tIME_hour;
223     public int tIME_minute;
224     public int tIME_second;
225 
226     // tRNS chunk
227     // If external (non-PNG sourced) data has red = green = blue,
228     // always store it as gray and promote when writing
229     public boolean tRNS_present;
230     public int tRNS_colorType; // PNG_COLOR_GRAY, _RGB, or _PALETTE
231     public byte[] tRNS_alpha; // May have fewer entries than PLTE_red, etc.
232     public int tRNS_gray;
233     public int tRNS_red;
234     public int tRNS_green;
235     public int tRNS_blue;
236 
237     // zTXt chunk
238     public ArrayList<String> zTXt_keyword = new ArrayList<String>();
239     public ArrayList<Integer> zTXt_compressionMethod = new ArrayList<Integer>();
240     public ArrayList<String> zTXt_text = new ArrayList<String>();
241 
242     // Unknown chunks
243     public ArrayList<String> unknownChunkType = new ArrayList<String>();
244     public ArrayList<byte[]> unknownChunkData = new ArrayList<byte[]>();
245 
246     public PNGMetadata() {
247         super(true,
248               nativeMetadataFormatName,
249               nativeMetadataFormatClassName,
250               null, null);
251     }
252 
253     public PNGMetadata(IIOMetadata metadata) {
254         // TODO -- implement
255     }
256 
257     /**
258      * Sets the IHDR_bitDepth and IHDR_colorType variables.
259      * The <code>numBands</code> parameter is necessary since
260      * we may only be writing a subset of the image bands.
261      */
262     public void initialize(ImageTypeSpecifier imageType, int numBands) {
263         ColorModel colorModel = imageType.getColorModel();
264         SampleModel sampleModel = imageType.getSampleModel();
265 
266         // Initialize IHDR_bitDepth
267         int[] sampleSize = sampleModel.getSampleSize();
268         int bitDepth = sampleSize[0];
269         // Choose max bit depth over all channels
270         // Fixes bug 4413109
271         for (int i = 1; i < sampleSize.length; i++) {
272             if (sampleSize[i] > bitDepth) {
273                 bitDepth = sampleSize[i];
274             }
275         }
276         // Multi-channel images must have a bit depth of 8 or 16
277         if (sampleSize.length > 1 && bitDepth < 8) {
278             bitDepth = 8;
279         }
280 
281         // Round bit depth up to a power of 2
282         if (bitDepth > 2 && bitDepth < 4) {
283             bitDepth = 4;
284         } else if (bitDepth > 4 && bitDepth < 8) {
285             bitDepth = 8;
286         } else if (bitDepth > 8 && bitDepth < 16) {
287             bitDepth = 16;
288         } else if (bitDepth > 16) {
289             throw new RuntimeException("bitDepth > 16!");
290         }
291         IHDR_bitDepth = bitDepth;
292 
293         // Initialize IHDR_colorType
294         if (colorModel instanceof IndexColorModel) {
295             IndexColorModel icm = (IndexColorModel)colorModel;
296             int size = icm.getMapSize();
297 
298             byte[] reds = new byte[size];
299             icm.getReds(reds);
300             byte[] greens = new byte[size];
301             icm.getGreens(greens);
302             byte[] blues = new byte[size];
303             icm.getBlues(blues);
304 
305             // Determine whether the color tables are actually a gray ramp
306             // if the color type has not been set previously
307             boolean isGray = false;
308             if (!IHDR_present ||
309                 (IHDR_colorType != PNGImageReader.PNG_COLOR_PALETTE)) {
310                 isGray = true;
311                 int scale = 255/((1 << IHDR_bitDepth) - 1);
312                 for (int i = 0; i < size; i++) {
313                     byte red = reds[i];
314                     if ((red != (byte)(i*scale)) ||
315                         (red != greens[i]) ||
316                         (red != blues[i])) {
317                         isGray = false;
318                         break;
319                     }
320                 }
321             }
322 
323             // Determine whether transparency exists
324             boolean hasAlpha = colorModel.hasAlpha();
325 
326             byte[] alpha = null;
327             if (hasAlpha) {
328                 alpha = new byte[size];
329                 icm.getAlphas(alpha);
330             }
331 
332             /*
333              * NB: PNG_COLOR_GRAY_ALPHA color type may be not optimal for images
334              * contained more than 1024 pixels (or even than 768 pixels in case of
335              * single transparent pixel in palette).
336              * For such images alpha samples in raster will occupy more space than
337              * it is required to store palette so it could be reasonable to
338              * use PNG_COLOR_PALETTE color type for large images.
339              */
340 
341             if (isGray && hasAlpha && (bitDepth == 8 || bitDepth == 16)) {
342                 IHDR_colorType = PNGImageReader.PNG_COLOR_GRAY_ALPHA;
343             } else if (isGray && !hasAlpha) {
344                 IHDR_colorType = PNGImageReader.PNG_COLOR_GRAY;
345             } else {
346                 IHDR_colorType = PNGImageReader.PNG_COLOR_PALETTE;
347                 PLTE_present = true;
348                 PLTE_order = null;
349                 PLTE_red = (byte[])reds.clone();
350                 PLTE_green = (byte[])greens.clone();
351                 PLTE_blue = (byte[])blues.clone();
352 
353                 if (hasAlpha) {
354                     tRNS_present = true;
355                     tRNS_colorType = PNGImageReader.PNG_COLOR_PALETTE;
356 
357                     PLTE_order = new int[alpha.length];
358 
359                     // Reorder the palette so that non-opaque entries
360                     // come first.  Since the tRNS chunk does not have
361                     // to store trailing 255's, this can save a
362                     // considerable amount of space when encoding
363                     // images with only one transparent pixel value,
364                     // e.g., images from GIF sources.
365 
366                     byte[] newAlpha = new byte[alpha.length];
367 
368                     // Scan for non-opaque entries and assign them
369                     // positions starting at 0.
370                     int newIndex = 0;
371                     for (int i = 0; i < alpha.length; i++) {
372                         if (alpha[i] != (byte)255) {
373                             PLTE_order[i] = newIndex;
374                             newAlpha[newIndex] = alpha[i];
375                             ++newIndex;
376                         }
377                     }
378                     int numTransparent = newIndex;
379 
380                     // Scan for opaque entries and assign them
381                     // positions following the non-opaque entries.
382                     for (int i = 0; i < alpha.length; i++) {
383                         if (alpha[i] == (byte)255) {
384                             PLTE_order[i] = newIndex++;
385                         }
386                     }
387 
388                     // Reorder the palettes
389                     byte[] oldRed = PLTE_red;
390                     byte[] oldGreen = PLTE_green;
391                     byte[] oldBlue = PLTE_blue;
392                     int len = oldRed.length; // All have the same length
393                     PLTE_red = new byte[len];
394                     PLTE_green = new byte[len];
395                     PLTE_blue = new byte[len];
396                     for (int i = 0; i < len; i++) {
397                         PLTE_red[PLTE_order[i]] = oldRed[i];
398                         PLTE_green[PLTE_order[i]] = oldGreen[i];
399                         PLTE_blue[PLTE_order[i]] = oldBlue[i];
400                     }
401 
402                     // Copy only the transparent entries into tRNS_alpha
403                     tRNS_alpha = new byte[numTransparent];
404                     System.arraycopy(newAlpha, 0,
405                                      tRNS_alpha, 0, numTransparent);
406                 }
407             }
408         } else {
409             if (numBands == 1) {
410                 IHDR_colorType = PNGImageReader.PNG_COLOR_GRAY;
411             } else if (numBands == 2) {
412                 IHDR_colorType = PNGImageReader.PNG_COLOR_GRAY_ALPHA;
413             } else if (numBands == 3) {
414                 IHDR_colorType = PNGImageReader.PNG_COLOR_RGB;
415             } else if (numBands == 4) {
416                 IHDR_colorType = PNGImageReader.PNG_COLOR_RGB_ALPHA;
417             } else {
418                 throw new RuntimeException("Number of bands not 1-4!");
419             }
420         }
421 
422         IHDR_present = true;
423     }
424 
425     public boolean isReadOnly() {
426         return false;
427     }
428 
429     private ArrayList<byte[]> cloneBytesArrayList(ArrayList<byte[]> in) {
430         if (in == null) {
431             return null;
432         } else {
433             ArrayList<byte[]> list = new ArrayList<byte[]>(in.size());
434             for (byte[] b: in) {
435                 list.add((b == null) ? null : (byte[])b.clone());
436             }
437             return list;
438         }
439     }
440 
441     // Deep clone
442     public Object clone() {
443         PNGMetadata metadata;
444         try {
445             metadata = (PNGMetadata)super.clone();
446         } catch (CloneNotSupportedException e) {
447             return null;
448         }
449 
450         // unknownChunkData needs deep clone
451         metadata.unknownChunkData =
452             cloneBytesArrayList(this.unknownChunkData);
453 
454         return metadata;
455     }
456 
457     public Node getAsTree(String formatName) {
458         if (formatName.equals(nativeMetadataFormatName)) {
459             return getNativeTree();
460         } else if (formatName.equals
461                    (IIOMetadataFormatImpl.standardMetadataFormatName)) {
462             return getStandardTree();
463         } else {
464             throw new IllegalArgumentException("Not a recognized format!");
465         }
466     }
467 
468     private Node getNativeTree() {
469         IIOMetadataNode node = null; // scratch node
470         IIOMetadataNode root = new IIOMetadataNode(nativeMetadataFormatName);
471 
472         // IHDR
473         if (IHDR_present) {
474             IIOMetadataNode IHDR_node = new IIOMetadataNode("IHDR");
475             IHDR_node.setAttribute("width", Integer.toString(IHDR_width));
476             IHDR_node.setAttribute("height", Integer.toString(IHDR_height));
477             IHDR_node.setAttribute("bitDepth",
478                                    Integer.toString(IHDR_bitDepth));
479             IHDR_node.setAttribute("colorType",
480                                    IHDR_colorTypeNames[IHDR_colorType]);
481             // IHDR_compressionMethod must be 0 in PNG 1.1
482             IHDR_node.setAttribute("compressionMethod",
483                           IHDR_compressionMethodNames[IHDR_compressionMethod]);
484             // IHDR_filterMethod must be 0 in PNG 1.1
485             IHDR_node.setAttribute("filterMethod",
486                                     IHDR_filterMethodNames[IHDR_filterMethod]);
487             IHDR_node.setAttribute("interlaceMethod",
488                               IHDR_interlaceMethodNames[IHDR_interlaceMethod]);
489             root.appendChild(IHDR_node);
490         }
491 
492         // PLTE
493         if (PLTE_present) {
494             IIOMetadataNode PLTE_node = new IIOMetadataNode("PLTE");
495             int numEntries = PLTE_red.length;
496             for (int i = 0; i < numEntries; i++) {
497                 IIOMetadataNode entry = new IIOMetadataNode("PLTEEntry");
498                 entry.setAttribute("index", Integer.toString(i));
499                 entry.setAttribute("red",
500                                    Integer.toString(PLTE_red[i] & 0xff));
501                 entry.setAttribute("green",
502                                    Integer.toString(PLTE_green[i] & 0xff));
503                 entry.setAttribute("blue",
504                                    Integer.toString(PLTE_blue[i] & 0xff));
505                 PLTE_node.appendChild(entry);
506             }
507 
508             root.appendChild(PLTE_node);
509         }
510 
511         // bKGD
512         if (bKGD_present) {
513             IIOMetadataNode bKGD_node = new IIOMetadataNode("bKGD");
514 
515             if (bKGD_colorType == PNGImageReader.PNG_COLOR_PALETTE) {
516                 node = new IIOMetadataNode("bKGD_Palette");
517                 node.setAttribute("index", Integer.toString(bKGD_index));
518             } else if (bKGD_colorType == PNGImageReader.PNG_COLOR_GRAY) {
519                 node = new IIOMetadataNode("bKGD_Grayscale");
520                 node.setAttribute("gray", Integer.toString(bKGD_gray));
521             } else if (bKGD_colorType == PNGImageReader.PNG_COLOR_RGB) {
522                 node = new IIOMetadataNode("bKGD_RGB");
523                 node.setAttribute("red", Integer.toString(bKGD_red));
524                 node.setAttribute("green", Integer.toString(bKGD_green));
525                 node.setAttribute("blue", Integer.toString(bKGD_blue));
526             }
527             bKGD_node.appendChild(node);
528 
529             root.appendChild(bKGD_node);
530         }
531 
532         // cHRM
533         if (cHRM_present) {
534             IIOMetadataNode cHRM_node = new IIOMetadataNode("cHRM");
535             cHRM_node.setAttribute("whitePointX",
536                               Integer.toString(cHRM_whitePointX));
537             cHRM_node.setAttribute("whitePointY",
538                               Integer.toString(cHRM_whitePointY));
539             cHRM_node.setAttribute("redX", Integer.toString(cHRM_redX));
540             cHRM_node.setAttribute("redY", Integer.toString(cHRM_redY));
541             cHRM_node.setAttribute("greenX", Integer.toString(cHRM_greenX));
542             cHRM_node.setAttribute("greenY", Integer.toString(cHRM_greenY));
543             cHRM_node.setAttribute("blueX", Integer.toString(cHRM_blueX));
544             cHRM_node.setAttribute("blueY", Integer.toString(cHRM_blueY));
545 
546             root.appendChild(cHRM_node);
547         }
548 
549         // gAMA
550         if (gAMA_present) {
551             IIOMetadataNode gAMA_node = new IIOMetadataNode("gAMA");
552             gAMA_node.setAttribute("value", Integer.toString(gAMA_gamma));
553 
554             root.appendChild(gAMA_node);
555         }
556 
557         // hIST
558         if (hIST_present) {
559             IIOMetadataNode hIST_node = new IIOMetadataNode("hIST");
560 
561             for (int i = 0; i < hIST_histogram.length; i++) {
562                 IIOMetadataNode hist =
563                     new IIOMetadataNode("hISTEntry");
564                 hist.setAttribute("index", Integer.toString(i));
565                 hist.setAttribute("value",
566                                   Integer.toString(hIST_histogram[i]));
567                 hIST_node.appendChild(hist);
568             }
569 
570             root.appendChild(hIST_node);
571         }
572 
573         // iCCP
574         if (iCCP_present) {
575             IIOMetadataNode iCCP_node = new IIOMetadataNode("iCCP");
576             iCCP_node.setAttribute("profileName", iCCP_profileName);
577             iCCP_node.setAttribute("compressionMethod",
578                           iCCP_compressionMethodNames[iCCP_compressionMethod]);
579 
580             Object profile = iCCP_compressedProfile;
581             if (profile != null) {
582                 profile = ((byte[])profile).clone();
583             }
584             iCCP_node.setUserObject(profile);
585 
586             root.appendChild(iCCP_node);
587         }
588 
589         // iTXt
590         if (iTXt_keyword.size() > 0) {
591             IIOMetadataNode iTXt_parent = new IIOMetadataNode("iTXt");
592             for (int i = 0; i < iTXt_keyword.size(); i++) {
593                 IIOMetadataNode iTXt_node = new IIOMetadataNode("iTXtEntry");
594                 iTXt_node.setAttribute("keyword", iTXt_keyword.get(i));
595                 iTXt_node.setAttribute("compressionFlag",
596                         iTXt_compressionFlag.get(i) ? "TRUE" : "FALSE");
597                 iTXt_node.setAttribute("compressionMethod",
598                         iTXt_compressionMethod.get(i).toString());
599                 iTXt_node.setAttribute("languageTag",
600                                        iTXt_languageTag.get(i));
601                 iTXt_node.setAttribute("translatedKeyword",
602                                        iTXt_translatedKeyword.get(i));
603                 iTXt_node.setAttribute("text", iTXt_text.get(i));
604 
605                 iTXt_parent.appendChild(iTXt_node);
606             }
607 
608             root.appendChild(iTXt_parent);
609         }
610 
611         // pHYs
612         if (pHYs_present) {
613             IIOMetadataNode pHYs_node = new IIOMetadataNode("pHYs");
614             pHYs_node.setAttribute("pixelsPerUnitXAxis",
615                               Integer.toString(pHYs_pixelsPerUnitXAxis));
616             pHYs_node.setAttribute("pixelsPerUnitYAxis",
617                                    Integer.toString(pHYs_pixelsPerUnitYAxis));
618             pHYs_node.setAttribute("unitSpecifier",
619                                    unitSpecifierNames[pHYs_unitSpecifier]);
620 
621             root.appendChild(pHYs_node);
622         }
623 
624         // sBIT
625         if (sBIT_present) {
626             IIOMetadataNode sBIT_node = new IIOMetadataNode("sBIT");
627 
628             if (sBIT_colorType == PNGImageReader.PNG_COLOR_GRAY) {
629                 node = new IIOMetadataNode("sBIT_Grayscale");
630                 node.setAttribute("gray",
631                                   Integer.toString(sBIT_grayBits));
632             } else if (sBIT_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) {
633                 node = new IIOMetadataNode("sBIT_GrayAlpha");
634                 node.setAttribute("gray",
635                                   Integer.toString(sBIT_grayBits));
636                 node.setAttribute("alpha",
637                                   Integer.toString(sBIT_alphaBits));
638             } else if (sBIT_colorType == PNGImageReader.PNG_COLOR_RGB) {
639                 node = new IIOMetadataNode("sBIT_RGB");
640                 node.setAttribute("red",
641                                   Integer.toString(sBIT_redBits));
642                 node.setAttribute("green",
643                                   Integer.toString(sBIT_greenBits));
644                 node.setAttribute("blue",
645                                   Integer.toString(sBIT_blueBits));
646             } else if (sBIT_colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA) {
647                 node = new IIOMetadataNode("sBIT_RGBAlpha");
648                 node.setAttribute("red",
649                                   Integer.toString(sBIT_redBits));
650                 node.setAttribute("green",
651                                   Integer.toString(sBIT_greenBits));
652                 node.setAttribute("blue",
653                                   Integer.toString(sBIT_blueBits));
654                 node.setAttribute("alpha",
655                                   Integer.toString(sBIT_alphaBits));
656             } else if (sBIT_colorType == PNGImageReader.PNG_COLOR_PALETTE) {
657                 node = new IIOMetadataNode("sBIT_Palette");
658                 node.setAttribute("red",
659                                   Integer.toString(sBIT_redBits));
660                 node.setAttribute("green",
661                                   Integer.toString(sBIT_greenBits));
662                 node.setAttribute("blue",
663                                   Integer.toString(sBIT_blueBits));
664             }
665             sBIT_node.appendChild(node);
666 
667             root.appendChild(sBIT_node);
668         }
669 
670         // sPLT
671         if (sPLT_present) {
672             IIOMetadataNode sPLT_node = new IIOMetadataNode("sPLT");
673 
674             sPLT_node.setAttribute("name", sPLT_paletteName);
675             sPLT_node.setAttribute("sampleDepth",
676                                    Integer.toString(sPLT_sampleDepth));
677 
678             int numEntries = sPLT_red.length;
679             for (int i = 0; i < numEntries; i++) {
680                 IIOMetadataNode entry = new IIOMetadataNode("sPLTEntry");
681                 entry.setAttribute("index", Integer.toString(i));
682                 entry.setAttribute("red", Integer.toString(sPLT_red[i]));
683                 entry.setAttribute("green", Integer.toString(sPLT_green[i]));
684                 entry.setAttribute("blue", Integer.toString(sPLT_blue[i]));
685                 entry.setAttribute("alpha", Integer.toString(sPLT_alpha[i]));
686                 entry.setAttribute("frequency",
687                                   Integer.toString(sPLT_frequency[i]));
688                 sPLT_node.appendChild(entry);
689             }
690 
691             root.appendChild(sPLT_node);
692         }
693 
694         // sRGB
695         if (sRGB_present) {
696             IIOMetadataNode sRGB_node = new IIOMetadataNode("sRGB");
697             sRGB_node.setAttribute("renderingIntent",
698                                    renderingIntentNames[sRGB_renderingIntent]);
699 
700             root.appendChild(sRGB_node);
701         }
702 
703         // tEXt
704         if (tEXt_keyword.size() > 0) {
705             IIOMetadataNode tEXt_parent = new IIOMetadataNode("tEXt");
706             for (int i = 0; i < tEXt_keyword.size(); i++) {
707                 IIOMetadataNode tEXt_node = new IIOMetadataNode("tEXtEntry");
708                 tEXt_node.setAttribute("keyword" , (String)tEXt_keyword.get(i));
709                 tEXt_node.setAttribute("value" , (String)tEXt_text.get(i));
710 
711                 tEXt_parent.appendChild(tEXt_node);
712             }
713 
714             root.appendChild(tEXt_parent);
715         }
716 
717         // tIME
718         if (tIME_present) {
719             IIOMetadataNode tIME_node = new IIOMetadataNode("tIME");
720             tIME_node.setAttribute("year", Integer.toString(tIME_year));
721             tIME_node.setAttribute("month", Integer.toString(tIME_month));
722             tIME_node.setAttribute("day", Integer.toString(tIME_day));
723             tIME_node.setAttribute("hour", Integer.toString(tIME_hour));
724             tIME_node.setAttribute("minute", Integer.toString(tIME_minute));
725             tIME_node.setAttribute("second", Integer.toString(tIME_second));
726 
727             root.appendChild(tIME_node);
728         }
729 
730         // tRNS
731         if (tRNS_present) {
732             IIOMetadataNode tRNS_node = new IIOMetadataNode("tRNS");
733 
734             if (tRNS_colorType == PNGImageReader.PNG_COLOR_PALETTE) {
735                 node = new IIOMetadataNode("tRNS_Palette");
736 
737                 for (int i = 0; i < tRNS_alpha.length; i++) {
738                     IIOMetadataNode entry =
739                         new IIOMetadataNode("tRNS_PaletteEntry");
740                     entry.setAttribute("index", Integer.toString(i));
741                     entry.setAttribute("alpha",
742                                        Integer.toString(tRNS_alpha[i] & 0xff));
743                     node.appendChild(entry);
744                 }
745             } else if (tRNS_colorType == PNGImageReader.PNG_COLOR_GRAY) {
746                 node = new IIOMetadataNode("tRNS_Grayscale");
747                 node.setAttribute("gray", Integer.toString(tRNS_gray));
748             } else if (tRNS_colorType == PNGImageReader.PNG_COLOR_RGB) {
749                 node = new IIOMetadataNode("tRNS_RGB");
750                 node.setAttribute("red", Integer.toString(tRNS_red));
751                 node.setAttribute("green", Integer.toString(tRNS_green));
752                 node.setAttribute("blue", Integer.toString(tRNS_blue));
753             }
754             tRNS_node.appendChild(node);
755 
756             root.appendChild(tRNS_node);
757         }
758 
759         // zTXt
760         if (zTXt_keyword.size() > 0) {
761             IIOMetadataNode zTXt_parent = new IIOMetadataNode("zTXt");
762             for (int i = 0; i < zTXt_keyword.size(); i++) {
763                 IIOMetadataNode zTXt_node = new IIOMetadataNode("zTXtEntry");
764                 zTXt_node.setAttribute("keyword", (String)zTXt_keyword.get(i));
765 
766                 int cm = ((Integer)zTXt_compressionMethod.get(i)).intValue();
767                 zTXt_node.setAttribute("compressionMethod",
768                                        zTXt_compressionMethodNames[cm]);
769 
770                 zTXt_node.setAttribute("text", (String)zTXt_text.get(i));
771 
772                 zTXt_parent.appendChild(zTXt_node);
773             }
774 
775             root.appendChild(zTXt_parent);
776         }
777 
778         // Unknown chunks
779         if (unknownChunkType.size() > 0) {
780             IIOMetadataNode unknown_parent =
781                 new IIOMetadataNode("UnknownChunks");
782             for (int i = 0; i < unknownChunkType.size(); i++) {
783                 IIOMetadataNode unknown_node =
784                     new IIOMetadataNode("UnknownChunk");
785                 unknown_node.setAttribute("type",
786                                           (String)unknownChunkType.get(i));
787                 unknown_node.setUserObject((byte[])unknownChunkData.get(i));
788 
789                 unknown_parent.appendChild(unknown_node);
790             }
791 
792             root.appendChild(unknown_parent);
793         }
794 
795         return root;
796     }
797 
798     private int getNumChannels() {
799         // Determine number of channels
800         // Be careful about palette color with transparency
801         int numChannels = IHDR_numChannels[IHDR_colorType];
802         if (IHDR_colorType == PNGImageReader.PNG_COLOR_PALETTE &&
803             tRNS_present && tRNS_colorType == IHDR_colorType) {
804             numChannels = 4;
805         }
806         return numChannels;
807     }
808 
809     public IIOMetadataNode getStandardChromaNode() {
810         IIOMetadataNode chroma_node = new IIOMetadataNode("Chroma");
811         IIOMetadataNode node = null; // scratch node
812 
813         node = new IIOMetadataNode("ColorSpaceType");
814         node.setAttribute("name", colorSpaceTypeNames[IHDR_colorType]);
815         chroma_node.appendChild(node);
816 
817         node = new IIOMetadataNode("NumChannels");
818         node.setAttribute("value", Integer.toString(getNumChannels()));
819         chroma_node.appendChild(node);
820 
821         if (gAMA_present) {
822             node = new IIOMetadataNode("Gamma");
823             node.setAttribute("value", Float.toString(gAMA_gamma*1.0e-5F));
824             chroma_node.appendChild(node);
825         }
826 
827         node = new IIOMetadataNode("BlackIsZero");
828         node.setAttribute("value", "TRUE");
829         chroma_node.appendChild(node);
830 
831         if (PLTE_present) {
832             boolean hasAlpha = tRNS_present &&
833                 (tRNS_colorType == PNGImageReader.PNG_COLOR_PALETTE);
834 
835             node = new IIOMetadataNode("Palette");
836             for (int i = 0; i < PLTE_red.length; i++) {
837                 IIOMetadataNode entry =
838                     new IIOMetadataNode("PaletteEntry");
839                 entry.setAttribute("index", Integer.toString(i));
840                 entry.setAttribute("red",
841                                    Integer.toString(PLTE_red[i] & 0xff));
842                 entry.setAttribute("green",
843                                    Integer.toString(PLTE_green[i] & 0xff));
844                 entry.setAttribute("blue",
845                                    Integer.toString(PLTE_blue[i] & 0xff));
846                 if (hasAlpha) {
847                     int alpha = (i < tRNS_alpha.length) ?
848                         (tRNS_alpha[i] & 0xff) : 255;
849                     entry.setAttribute("alpha", Integer.toString(alpha));
850                 }
851                 node.appendChild(entry);
852             }
853             chroma_node.appendChild(node);
854         }
855 
856         if (bKGD_present) {
857             if (bKGD_colorType == PNGImageReader.PNG_COLOR_PALETTE) {
858                 node = new IIOMetadataNode("BackgroundIndex");
859                 node.setAttribute("value", Integer.toString(bKGD_index));
860             } else {
861                 node = new IIOMetadataNode("BackgroundColor");
862                 int r, g, b;
863 
864                 if (bKGD_colorType == PNGImageReader.PNG_COLOR_GRAY) {
865                     r = g = b = bKGD_gray;
866                 } else {
867                     r = bKGD_red;
868                     g = bKGD_green;
869                     b = bKGD_blue;
870                 }
871                 node.setAttribute("red", Integer.toString(r));
872                 node.setAttribute("green", Integer.toString(g));
873                 node.setAttribute("blue", Integer.toString(b));
874             }
875             chroma_node.appendChild(node);
876         }
877 
878         return chroma_node;
879     }
880 
881     public IIOMetadataNode getStandardCompressionNode() {
882         IIOMetadataNode compression_node = new IIOMetadataNode("Compression");
883         IIOMetadataNode node = null; // scratch node
884 
885         node = new IIOMetadataNode("CompressionTypeName");
886         node.setAttribute("value", "deflate");
887         compression_node.appendChild(node);
888 
889         node = new IIOMetadataNode("Lossless");
890         node.setAttribute("value", "TRUE");
891         compression_node.appendChild(node);
892 
893         node = new IIOMetadataNode("NumProgressiveScans");
894         node.setAttribute("value",
895                           (IHDR_interlaceMethod == 0) ? "1" : "7");
896         compression_node.appendChild(node);
897 
898         return compression_node;
899     }
900 
901     private String repeat(String s, int times) {
902         if (times == 1) {
903             return s;
904         }
905         StringBuffer sb = new StringBuffer((s.length() + 1)*times - 1);
906         sb.append(s);
907         for (int i = 1; i < times; i++) {
908             sb.append(" ");
909             sb.append(s);
910         }
911         return sb.toString();
912     }
913 
914     public IIOMetadataNode getStandardDataNode() {
915         IIOMetadataNode data_node = new IIOMetadataNode("Data");
916         IIOMetadataNode node = null; // scratch node
917 
918         node = new IIOMetadataNode("PlanarConfiguration");
919         node.setAttribute("value", "PixelInterleaved");
920         data_node.appendChild(node);
921 
922         node = new IIOMetadataNode("SampleFormat");
923         node.setAttribute("value",
924                           IHDR_colorType == PNGImageReader.PNG_COLOR_PALETTE ?
925                           "Index" : "UnsignedIntegral");
926         data_node.appendChild(node);
927 
928         String bitDepth = Integer.toString(IHDR_bitDepth);
929         node = new IIOMetadataNode("BitsPerSample");
930         node.setAttribute("value", repeat(bitDepth, getNumChannels()));
931         data_node.appendChild(node);
932 
933         if (sBIT_present) {
934             node = new IIOMetadataNode("SignificantBitsPerSample");
935             String sbits;
936             if (sBIT_colorType == PNGImageReader.PNG_COLOR_GRAY ||
937                 sBIT_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) {
938                 sbits = Integer.toString(sBIT_grayBits);
939             } else { // sBIT_colorType == PNGImageReader.PNG_COLOR_RGB ||
940                      // sBIT_colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA
941                 sbits = Integer.toString(sBIT_redBits) + " " +
942                     Integer.toString(sBIT_greenBits) + " " +
943                     Integer.toString(sBIT_blueBits);
944             }
945 
946             if (sBIT_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA ||
947                 sBIT_colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA) {
948                 sbits += " " + Integer.toString(sBIT_alphaBits);
949             }
950 
951             node.setAttribute("value", sbits);
952             data_node.appendChild(node);
953         }
954 
955         // SampleMSB
956 
957         return data_node;
958     }
959 
960     public IIOMetadataNode getStandardDimensionNode() {
961         IIOMetadataNode dimension_node = new IIOMetadataNode("Dimension");
962         IIOMetadataNode node = null; // scratch node
963 
964         node = new IIOMetadataNode("PixelAspectRatio");
965         float ratio = pHYs_present ?
966             (float)pHYs_pixelsPerUnitXAxis/pHYs_pixelsPerUnitYAxis : 1.0F;
967         node.setAttribute("value", Float.toString(ratio));
968         dimension_node.appendChild(node);
969 
970         node = new IIOMetadataNode("ImageOrientation");
971         node.setAttribute("value", "Normal");
972         dimension_node.appendChild(node);
973 
974         if (pHYs_present && pHYs_unitSpecifier == PHYS_UNIT_METER) {
975             node = new IIOMetadataNode("HorizontalPixelSize");
976             node.setAttribute("value",
977                               Float.toString(1000.0F/pHYs_pixelsPerUnitXAxis));
978             dimension_node.appendChild(node);
979 
980             node = new IIOMetadataNode("VerticalPixelSize");
981             node.setAttribute("value",
982                               Float.toString(1000.0F/pHYs_pixelsPerUnitYAxis));
983             dimension_node.appendChild(node);
984         }
985 
986         return dimension_node;
987     }
988 
989     public IIOMetadataNode getStandardDocumentNode() {
990         if (!tIME_present) {
991             return null;
992         }
993 
994         IIOMetadataNode document_node = new IIOMetadataNode("Document");
995         IIOMetadataNode node = null; // scratch node
996 
997         node = new IIOMetadataNode("ImageModificationTime");
998         node.setAttribute("year", Integer.toString(tIME_year));
999         node.setAttribute("month", Integer.toString(tIME_month));
1000         node.setAttribute("day", Integer.toString(tIME_day));
1001         node.setAttribute("hour", Integer.toString(tIME_hour));
1002         node.setAttribute("minute", Integer.toString(tIME_minute));
1003         node.setAttribute("second", Integer.toString(tIME_second));
1004         document_node.appendChild(node);
1005 
1006         return document_node;
1007     }
1008 
1009     public IIOMetadataNode getStandardTextNode() {
1010         int numEntries = tEXt_keyword.size() +
1011             iTXt_keyword.size() + zTXt_keyword.size();
1012         if (numEntries == 0) {
1013             return null;
1014         }
1015 
1016         IIOMetadataNode text_node = new IIOMetadataNode("Text");
1017         IIOMetadataNode node = null; // scratch node
1018 
1019         for (int i = 0; i < tEXt_keyword.size(); i++) {
1020             node = new IIOMetadataNode("TextEntry");
1021             node.setAttribute("keyword", (String)tEXt_keyword.get(i));
1022             node.setAttribute("value", (String)tEXt_text.get(i));
1023             node.setAttribute("encoding", "ISO-8859-1");
1024             node.setAttribute("compression", "none");
1025 
1026             text_node.appendChild(node);
1027         }
1028 
1029         for (int i = 0; i < iTXt_keyword.size(); i++) {
1030             node = new IIOMetadataNode("TextEntry");
1031             node.setAttribute("keyword", iTXt_keyword.get(i));
1032             node.setAttribute("value", iTXt_text.get(i));
1033             node.setAttribute("language",
1034                               iTXt_languageTag.get(i));
1035             if (iTXt_compressionFlag.get(i)) {
1036                 node.setAttribute("compression", "zip");
1037             } else {
1038                 node.setAttribute("compression", "none");
1039             }
1040 
1041             text_node.appendChild(node);
1042         }
1043 
1044         for (int i = 0; i < zTXt_keyword.size(); i++) {
1045             node = new IIOMetadataNode("TextEntry");
1046             node.setAttribute("keyword", (String)zTXt_keyword.get(i));
1047             node.setAttribute("value", (String)zTXt_text.get(i));
1048             node.setAttribute("compression", "zip");
1049 
1050             text_node.appendChild(node);
1051         }
1052 
1053         return text_node;
1054     }
1055 
1056     public IIOMetadataNode getStandardTransparencyNode() {
1057         IIOMetadataNode transparency_node =
1058             new IIOMetadataNode("Transparency");
1059         IIOMetadataNode node = null; // scratch node
1060 
1061         node = new IIOMetadataNode("Alpha");
1062         boolean hasAlpha =
1063             (IHDR_colorType == PNGImageReader.PNG_COLOR_RGB_ALPHA) ||
1064             (IHDR_colorType == PNGImageReader.PNG_COLOR_GRAY_ALPHA) ||
1065             (IHDR_colorType == PNGImageReader.PNG_COLOR_PALETTE &&
1066              tRNS_present &&
1067              (tRNS_colorType == IHDR_colorType) &&
1068              (tRNS_alpha != null));
1069         node.setAttribute("value", hasAlpha ? "nonpremultipled" : "none");
1070         transparency_node.appendChild(node);
1071 
1072         if (tRNS_present) {
1073             node = new IIOMetadataNode("TransparentColor");
1074             if (tRNS_colorType == PNGImageReader.PNG_COLOR_RGB) {
1075                 node.setAttribute("value",
1076                                   Integer.toString(tRNS_red) + " " +
1077                                   Integer.toString(tRNS_green) + " " +
1078                                   Integer.toString(tRNS_blue));
1079             } else if (tRNS_colorType == PNGImageReader.PNG_COLOR_GRAY) {
1080                 node.setAttribute("value", Integer.toString(tRNS_gray));
1081             }
1082             transparency_node.appendChild(node);
1083         }
1084 
1085         return transparency_node;
1086     }
1087 
1088     // Shorthand for throwing an IIOInvalidTreeException
1089     private void fatal(Node node, String reason)
1090         throws IIOInvalidTreeException {
1091         throw new IIOInvalidTreeException(reason, node);
1092     }
1093 
1094     // Get an integer-valued attribute
1095     private String getStringAttribute(Node node, String name,
1096                                       String defaultValue, boolean required)
1097         throws IIOInvalidTreeException {
1098         Node attr = node.getAttributes().getNamedItem(name);
1099         if (attr == null) {
1100             if (!required) {
1101                 return defaultValue;
1102             } else {
1103                 fatal(node, "Required attribute " + name + " not present!");
1104             }
1105         }
1106         return attr.getNodeValue();
1107     }
1108 
1109 
1110     // Get an integer-valued attribute
1111     private int getIntAttribute(Node node, String name,
1112                                 int defaultValue, boolean required)
1113         throws IIOInvalidTreeException {
1114         String value = getStringAttribute(node, name, null, required);
1115         if (value == null) {
1116             return defaultValue;
1117         }
1118         return Integer.parseInt(value);
1119     }
1120 
1121     // Get a float-valued attribute
1122     private float getFloatAttribute(Node node, String name,
1123                                     float defaultValue, boolean required)
1124         throws IIOInvalidTreeException {
1125         String value = getStringAttribute(node, name, null, required);
1126         if (value == null) {
1127             return defaultValue;
1128         }
1129         return Float.parseFloat(value);
1130     }
1131 
1132     // Get a required integer-valued attribute
1133     private int getIntAttribute(Node node, String name)
1134         throws IIOInvalidTreeException {
1135         return getIntAttribute(node, name, -1, true);
1136     }
1137 
1138     // Get a required float-valued attribute
1139     private float getFloatAttribute(Node node, String name)
1140         throws IIOInvalidTreeException {
1141         return getFloatAttribute(node, name, -1.0F, true);
1142     }
1143 
1144     // Get a boolean-valued attribute
1145     private boolean getBooleanAttribute(Node node, String name,
1146                                         boolean defaultValue,
1147                                         boolean required)
1148         throws IIOInvalidTreeException {
1149         Node attr = node.getAttributes().getNamedItem(name);
1150         if (attr == null) {
1151             if (!required) {
1152                 return defaultValue;
1153             } else {
1154                 fatal(node, "Required attribute " + name + " not present!");
1155             }
1156         }
1157         String value = attr.getNodeValue();
1158         // Allow lower case booleans for backward compatibility, #5082756
1159         if (value.equals("TRUE") || value.equals("true")) {
1160             return true;
1161         } else if (value.equals("FALSE") || value.equals("false")) {
1162             return false;
1163         } else {
1164             fatal(node, "Attribute " + name + " must be 'TRUE' or 'FALSE'!");
1165             return false;
1166         }
1167     }
1168 
1169     // Get a required boolean-valued attribute
1170     private boolean getBooleanAttribute(Node node, String name)
1171         throws IIOInvalidTreeException {
1172         return getBooleanAttribute(node, name, false, true);
1173     }
1174 
1175     // Get an enumerated attribute as an index into a String array
1176     private int getEnumeratedAttribute(Node node,
1177                                        String name, String[] legalNames,
1178                                        int defaultValue, boolean required)
1179         throws IIOInvalidTreeException {
1180         Node attr = node.getAttributes().getNamedItem(name);
1181         if (attr == null) {
1182             if (!required) {
1183                 return defaultValue;
1184             } else {
1185                 fatal(node, "Required attribute " + name + " not present!");
1186             }
1187         }
1188         String value = attr.getNodeValue();
1189         for (int i = 0; i < legalNames.length; i++) {
1190             if (value.equals(legalNames[i])) {
1191                 return i;
1192             }
1193         }
1194 
1195         fatal(node, "Illegal value for attribute " + name + "!");
1196         return -1;
1197     }
1198 
1199     // Get a required enumerated attribute as an index into a String array
1200     private int getEnumeratedAttribute(Node node,
1201                                        String name, String[] legalNames)
1202         throws IIOInvalidTreeException {
1203         return getEnumeratedAttribute(node, name, legalNames, -1, true);
1204     }
1205 
1206     // Get a String-valued attribute
1207     private String getAttribute(Node node, String name,
1208                                 String defaultValue, boolean required)
1209         throws IIOInvalidTreeException {
1210         Node attr = node.getAttributes().getNamedItem(name);
1211         if (attr == null) {
1212             if (!required) {
1213                 return defaultValue;
1214             } else {
1215                 fatal(node, "Required attribute " + name + " not present!");
1216             }
1217         }
1218         return attr.getNodeValue();
1219     }
1220 
1221     // Get a required String-valued attribute
1222     private String getAttribute(Node node, String name)
1223         throws IIOInvalidTreeException {
1224             return getAttribute(node, name, null, true);
1225     }
1226 
1227     public void mergeTree(String formatName, Node root)
1228         throws IIOInvalidTreeException {
1229         if (formatName.equals(nativeMetadataFormatName)) {
1230             if (root == null) {
1231                 throw new IllegalArgumentException("root == null!");
1232             }
1233             mergeNativeTree(root);
1234         } else if (formatName.equals
1235                    (IIOMetadataFormatImpl.standardMetadataFormatName)) {
1236             if (root == null) {
1237                 throw new IllegalArgumentException("root == null!");
1238             }
1239             mergeStandardTree(root);
1240         } else {
1241             throw new IllegalArgumentException("Not a recognized format!");
1242         }
1243     }
1244 
1245     private void mergeNativeTree(Node root)
1246         throws IIOInvalidTreeException {
1247         Node node = root;
1248         if (!node.getNodeName().equals(nativeMetadataFormatName)) {
1249             fatal(node, "Root must be " + nativeMetadataFormatName);
1250         }
1251 
1252         node = node.getFirstChild();
1253         while (node != null) {
1254             String name = node.getNodeName();
1255 
1256             if (name.equals("IHDR")) {
1257                 IHDR_width = getIntAttribute(node, "width");
1258                 IHDR_height = getIntAttribute(node, "height");
1259                 IHDR_bitDepth = getEnumeratedAttribute(node, "bitDepth",
1260                                                        IHDR_bitDepths);
1261                 IHDR_colorType = getEnumeratedAttribute(node, "colorType",
1262                                                         IHDR_colorTypeNames);
1263                 IHDR_compressionMethod =
1264                     getEnumeratedAttribute(node, "compressionMethod",
1265                                            IHDR_compressionMethodNames);
1266                 IHDR_filterMethod =
1267                     getEnumeratedAttribute(node,
1268                                            "filterMethod",
1269                                            IHDR_filterMethodNames);
1270                 IHDR_interlaceMethod =
1271                     getEnumeratedAttribute(node, "interlaceMethod",
1272                                            IHDR_interlaceMethodNames);
1273                 IHDR_present = true;
1274             } else if (name.equals("PLTE")) {
1275                 byte[] red = new byte[256];
1276                 byte[] green  = new byte[256];
1277                 byte[] blue = new byte[256];
1278                 int maxindex = -1;
1279 
1280                 Node PLTE_entry = node.getFirstChild();
1281                 if (PLTE_entry == null) {
1282                     fatal(node, "Palette has no entries!");
1283                 }
1284 
1285                 while (PLTE_entry != null) {
1286                     if (!PLTE_entry.getNodeName().equals("PLTEEntry")) {
1287                         fatal(node,
1288                               "Only a PLTEEntry may be a child of a PLTE!");
1289                     }
1290 
1291                     int index = getIntAttribute(PLTE_entry, "index");
1292                     if (index < 0 || index > 255) {
1293                         fatal(node,
1294                               "Bad value for PLTEEntry attribute index!");
1295                     }
1296                     if (index > maxindex) {
1297                         maxindex = index;
1298                     }
1299                     red[index] =
1300                         (byte)getIntAttribute(PLTE_entry, "red");
1301                     green[index] =
1302                         (byte)getIntAttribute(PLTE_entry, "green");
1303                     blue[index] =
1304                         (byte)getIntAttribute(PLTE_entry, "blue");
1305 
1306                     PLTE_entry = PLTE_entry.getNextSibling();
1307                 }
1308 
1309                 int numEntries = maxindex + 1;
1310                 PLTE_red = new byte[numEntries];
1311                 PLTE_green = new byte[numEntries];
1312                 PLTE_blue = new byte[numEntries];
1313                 System.arraycopy(red, 0, PLTE_red, 0, numEntries);
1314                 System.arraycopy(green, 0, PLTE_green, 0, numEntries);
1315                 System.arraycopy(blue, 0, PLTE_blue, 0, numEntries);
1316                 PLTE_present = true;
1317             } else if (name.equals("bKGD")) {
1318                 bKGD_present = false; // Guard against partial overwrite
1319                 Node bKGD_node = node.getFirstChild();
1320                 if (bKGD_node == null) {
1321                     fatal(node, "bKGD node has no children!");
1322                 }
1323                 String bKGD_name = bKGD_node.getNodeName();
1324                 if (bKGD_name.equals("bKGD_Palette")) {
1325                     bKGD_index = getIntAttribute(bKGD_node, "index");
1326                     bKGD_colorType = PNGImageReader.PNG_COLOR_PALETTE;
1327                 } else if (bKGD_name.equals("bKGD_Grayscale")) {
1328                     bKGD_gray = getIntAttribute(bKGD_node, "gray");
1329                     bKGD_colorType = PNGImageReader.PNG_COLOR_GRAY;
1330                 } else if (bKGD_name.equals("bKGD_RGB")) {
1331                     bKGD_red = getIntAttribute(bKGD_node, "red");
1332                     bKGD_green = getIntAttribute(bKGD_node, "green");
1333                     bKGD_blue = getIntAttribute(bKGD_node, "blue");
1334                     bKGD_colorType = PNGImageReader.PNG_COLOR_RGB;
1335                 } else {
1336                     fatal(node, "Bad child of a bKGD node!");
1337                 }
1338                 if (bKGD_node.getNextSibling() != null) {
1339                     fatal(node, "bKGD node has more than one child!");
1340                 }
1341 
1342                 bKGD_present = true;
1343             } else if (name.equals("cHRM")) {
1344                 cHRM_whitePointX = getIntAttribute(node, "whitePointX");
1345                 cHRM_whitePointY = getIntAttribute(node, "whitePointY");
1346                 cHRM_redX = getIntAttribute(node, "redX");
1347                 cHRM_redY = getIntAttribute(node, "redY");
1348                 cHRM_greenX = getIntAttribute(node, "greenX");
1349                 cHRM_greenY = getIntAttribute(node, "greenY");
1350                 cHRM_blueX = getIntAttribute(node, "blueX");
1351                 cHRM_blueY = getIntAttribute(node, "blueY");
1352 
1353                 cHRM_present = true;
1354             } else if (name.equals("gAMA")) {
1355                 gAMA_gamma = getIntAttribute(node, "value");
1356                 gAMA_present = true;
1357             } else if (name.equals("hIST")) {
1358                 char[] hist = new char[256];
1359                 int maxindex = -1;
1360 
1361                 Node hIST_entry = node.getFirstChild();
1362                 if (hIST_entry == null) {
1363                     fatal(node, "hIST node has no children!");
1364                 }
1365 
1366                 while (hIST_entry != null) {
1367                     if (!hIST_entry.getNodeName().equals("hISTEntry")) {
1368                         fatal(node,
1369                               "Only a hISTEntry may be a child of a hIST!");
1370                     }
1371 
1372                     int index = getIntAttribute(hIST_entry, "index");
1373                     if (index < 0 || index > 255) {
1374                         fatal(node,
1375                               "Bad value for histEntry attribute index!");
1376                     }
1377                     if (index > maxindex) {
1378                         maxindex = index;
1379                     }
1380                     hist[index] =
1381                         (char)getIntAttribute(hIST_entry, "value");
1382 
1383                     hIST_entry = hIST_entry.getNextSibling();
1384                 }
1385 
1386                 int numEntries = maxindex + 1;
1387                 hIST_histogram = new char[numEntries];
1388                 System.arraycopy(hist, 0, hIST_histogram, 0, numEntries);
1389 
1390                 hIST_present = true;
1391             } else if (name.equals("iCCP")) {
1392                 iCCP_profileName = getAttribute(node, "profileName");
1393                 iCCP_compressionMethod =
1394                     getEnumeratedAttribute(node, "compressionMethod",
1395                                            iCCP_compressionMethodNames);
1396                 Object compressedProfile =
1397                     ((IIOMetadataNode)node).getUserObject();
1398                 if (compressedProfile == null) {
1399                     fatal(node, "No ICCP profile present in user object!");
1400                 }
1401                 if (!(compressedProfile instanceof byte[])) {
1402                     fatal(node, "User object not a byte array!");
1403                 }
1404 
1405                 iCCP_compressedProfile =
1406                     (byte[])((byte[])compressedProfile).clone();
1407 
1408                 iCCP_present = true;
1409             } else if (name.equals("iTXt")) {
1410                 Node iTXt_node = node.getFirstChild();
1411                 while (iTXt_node != null) {
1412                     if (!iTXt_node.getNodeName().equals("iTXtEntry")) {
1413                         fatal(node,
1414                               "Only an iTXtEntry may be a child of an iTXt!");
1415                     }
1416 
1417                     String keyword = getAttribute(iTXt_node, "keyword");
1418                     if (isValidKeyword(keyword)) {
1419                         iTXt_keyword.add(keyword);
1420 
1421                         boolean compressionFlag =
1422                             getBooleanAttribute(iTXt_node, "compressionFlag");
1423                         iTXt_compressionFlag.add(Boolean.valueOf(compressionFlag));
1424 
1425                         String compressionMethod =
1426                             getAttribute(iTXt_node, "compressionMethod");
1427                         iTXt_compressionMethod.add(Integer.valueOf(compressionMethod));
1428 
1429                         String languageTag =
1430                             getAttribute(iTXt_node, "languageTag");
1431                         iTXt_languageTag.add(languageTag);
1432 
1433                         String translatedKeyword =
1434                             getAttribute(iTXt_node, "translatedKeyword");
1435                         iTXt_translatedKeyword.add(translatedKeyword);
1436 
1437                         String text = getAttribute(iTXt_node, "text");
1438                         iTXt_text.add(text);
1439 
1440                     }
1441                     // silently skip invalid text entry
1442 
1443                     iTXt_node = iTXt_node.getNextSibling();
1444                 }
1445             } else if (name.equals("pHYs")) {
1446                 pHYs_pixelsPerUnitXAxis =
1447                     getIntAttribute(node, "pixelsPerUnitXAxis");
1448                 pHYs_pixelsPerUnitYAxis =
1449                     getIntAttribute(node, "pixelsPerUnitYAxis");
1450                 pHYs_unitSpecifier =
1451                     getEnumeratedAttribute(node, "unitSpecifier",
1452                                            unitSpecifierNames);
1453 
1454                 pHYs_present = true;
1455             } else if (name.equals("sBIT")) {
1456                 sBIT_present = false; // Guard against partial overwrite
1457                 Node sBIT_node = node.getFirstChild();
1458                 if (sBIT_node == null) {
1459                     fatal(node, "sBIT node has no children!");
1460                 }
1461                 String sBIT_name = sBIT_node.getNodeName();
1462                 if (sBIT_name.equals("sBIT_Grayscale")) {
1463                     sBIT_grayBits = getIntAttribute(sBIT_node, "gray");
1464                     sBIT_colorType = PNGImageReader.PNG_COLOR_GRAY;
1465                 } else if (sBIT_name.equals("sBIT_GrayAlpha")) {
1466                     sBIT_grayBits = getIntAttribute(sBIT_node, "gray");
1467                     sBIT_alphaBits = getIntAttribute(sBIT_node, "alpha");
1468                     sBIT_colorType = PNGImageReader.PNG_COLOR_GRAY_ALPHA;
1469                 } else if (sBIT_name.equals("sBIT_RGB")) {
1470                     sBIT_redBits = getIntAttribute(sBIT_node, "red");
1471                     sBIT_greenBits = getIntAttribute(sBIT_node, "green");
1472                     sBIT_blueBits = getIntAttribute(sBIT_node, "blue");
1473                     sBIT_colorType = PNGImageReader.PNG_COLOR_RGB;
1474                 } else if (sBIT_name.equals("sBIT_RGBAlpha")) {
1475                     sBIT_redBits = getIntAttribute(sBIT_node, "red");
1476                     sBIT_greenBits = getIntAttribute(sBIT_node, "green");
1477                     sBIT_blueBits = getIntAttribute(sBIT_node, "blue");
1478                     sBIT_alphaBits = getIntAttribute(sBIT_node, "alpha");
1479                     sBIT_colorType = PNGImageReader.PNG_COLOR_RGB_ALPHA;
1480                 } else if (sBIT_name.equals("sBIT_Palette")) {
1481                     sBIT_redBits = getIntAttribute(sBIT_node, "red");
1482                     sBIT_greenBits = getIntAttribute(sBIT_node, "green");
1483                     sBIT_blueBits = getIntAttribute(sBIT_node, "blue");
1484                     sBIT_colorType = PNGImageReader.PNG_COLOR_PALETTE;
1485                 } else {
1486                     fatal(node, "Bad child of an sBIT node!");
1487                 }
1488                 if (sBIT_node.getNextSibling() != null) {
1489                     fatal(node, "sBIT node has more than one child!");
1490                 }
1491 
1492                 sBIT_present = true;
1493             } else if (name.equals("sPLT")) {
1494                 sPLT_paletteName = getAttribute(node, "name");
1495                 sPLT_sampleDepth = getIntAttribute(node, "sampleDepth");
1496 
1497                 int[] red = new int[256];
1498                 int[] green  = new int[256];
1499                 int[] blue = new int[256];
1500                 int[] alpha = new int[256];
1501                 int[] frequency = new int[256];
1502                 int maxindex = -1;
1503 
1504                 Node sPLT_entry = node.getFirstChild();
1505                 if (sPLT_entry == null) {
1506                     fatal(node, "sPLT node has no children!");
1507                 }
1508 
1509                 while (sPLT_entry != null) {
1510                     if (!sPLT_entry.getNodeName().equals("sPLTEntry")) {
1511                         fatal(node,
1512                               "Only an sPLTEntry may be a child of an sPLT!");
1513                     }
1514 
1515                     int index = getIntAttribute(sPLT_entry, "index");
1516                     if (index < 0 || index > 255) {
1517                         fatal(node,
1518                               "Bad value for PLTEEntry attribute index!");
1519                     }
1520                     if (index > maxindex) {
1521                         maxindex = index;
1522                     }
1523                     red[index] = getIntAttribute(sPLT_entry, "red");
1524                     green[index] = getIntAttribute(sPLT_entry, "green");
1525                     blue[index] = getIntAttribute(sPLT_entry, "blue");
1526                     alpha[index] = getIntAttribute(sPLT_entry, "alpha");
1527                     frequency[index] =
1528                         getIntAttribute(sPLT_entry, "frequency");
1529 
1530                     sPLT_entry = sPLT_entry.getNextSibling();
1531                 }
1532 
1533                 int numEntries = maxindex + 1;
1534                 sPLT_red = new int[numEntries];
1535                 sPLT_green = new int[numEntries];
1536                 sPLT_blue = new int[numEntries];
1537                 sPLT_alpha = new int[numEntries];
1538                 sPLT_frequency = new int[numEntries];
1539                 System.arraycopy(red, 0, sPLT_red, 0, numEntries);
1540                 System.arraycopy(green, 0, sPLT_green, 0, numEntries);
1541                 System.arraycopy(blue, 0, sPLT_blue, 0, numEntries);
1542                 System.arraycopy(alpha, 0, sPLT_alpha, 0, numEntries);
1543                 System.arraycopy(frequency, 0,
1544                                  sPLT_frequency, 0, numEntries);
1545 
1546                 sPLT_present = true;
1547             } else if (name.equals("sRGB")) {
1548                 sRGB_renderingIntent =
1549                     getEnumeratedAttribute(node, "renderingIntent",
1550                                            renderingIntentNames);
1551 
1552                 sRGB_present = true;
1553             } else if (name.equals("tEXt")) {
1554                 Node tEXt_node = node.getFirstChild();
1555                 while (tEXt_node != null) {
1556                     if (!tEXt_node.getNodeName().equals("tEXtEntry")) {
1557                         fatal(node,
1558                               "Only an tEXtEntry may be a child of an tEXt!");
1559                     }
1560 
1561                     String keyword = getAttribute(tEXt_node, "keyword");
1562                     tEXt_keyword.add(keyword);
1563 
1564                     String text = getAttribute(tEXt_node, "value");
1565                     tEXt_text.add(text);
1566 
1567                     tEXt_node = tEXt_node.getNextSibling();
1568                 }
1569             } else if (name.equals("tIME")) {
1570                 tIME_year = getIntAttribute(node, "year");
1571                 tIME_month = getIntAttribute(node, "month");
1572                 tIME_day = getIntAttribute(node, "day");
1573                 tIME_hour = getIntAttribute(node, "hour");
1574                 tIME_minute = getIntAttribute(node, "minute");
1575                 tIME_second = getIntAttribute(node, "second");
1576 
1577                 tIME_present = true;
1578             } else if (name.equals("tRNS")) {
1579                 tRNS_present = false; // Guard against partial overwrite
1580                 Node tRNS_node = node.getFirstChild();
1581                 if (tRNS_node == null) {
1582                     fatal(node, "tRNS node has no children!");
1583                 }
1584                 String tRNS_name = tRNS_node.getNodeName();
1585                 if (tRNS_name.equals("tRNS_Palette")) {
1586                     byte[] alpha = new byte[256];
1587                     int maxindex = -1;
1588 
1589                     Node tRNS_paletteEntry = tRNS_node.getFirstChild();
1590                     if (tRNS_paletteEntry == null) {
1591                         fatal(node, "tRNS_Palette node has no children!");
1592                     }
1593                     while (tRNS_paletteEntry != null) {
1594                         if (!tRNS_paletteEntry.getNodeName().equals(
1595                                                         "tRNS_PaletteEntry")) {
1596                             fatal(node,
1597                  "Only a tRNS_PaletteEntry may be a child of a tRNS_Palette!");
1598                         }
1599                         int index =
1600                             getIntAttribute(tRNS_paletteEntry, "index");
1601                         if (index < 0 || index > 255) {
1602                             fatal(node,
1603                            "Bad value for tRNS_PaletteEntry attribute index!");
1604                         }
1605                         if (index > maxindex) {
1606                             maxindex = index;
1607                         }
1608                         alpha[index] =
1609                             (byte)getIntAttribute(tRNS_paletteEntry,
1610                                                   "alpha");
1611 
1612                         tRNS_paletteEntry =
1613                             tRNS_paletteEntry.getNextSibling();
1614                     }
1615 
1616                     int numEntries = maxindex + 1;
1617                     tRNS_alpha = new byte[numEntries];
1618                     tRNS_colorType = PNGImageReader.PNG_COLOR_PALETTE;
1619                     System.arraycopy(alpha, 0, tRNS_alpha, 0, numEntries);
1620                 } else if (tRNS_name.equals("tRNS_Grayscale")) {
1621                     tRNS_gray = getIntAttribute(tRNS_node, "gray");
1622                     tRNS_colorType = PNGImageReader.PNG_COLOR_GRAY;
1623                 } else if (tRNS_name.equals("tRNS_RGB")) {
1624                     tRNS_red = getIntAttribute(tRNS_node, "red");
1625                     tRNS_green = getIntAttribute(tRNS_node, "green");
1626                     tRNS_blue = getIntAttribute(tRNS_node, "blue");
1627                     tRNS_colorType = PNGImageReader.PNG_COLOR_RGB;
1628                 } else {
1629                     fatal(node, "Bad child of a tRNS node!");
1630                 }
1631                 if (tRNS_node.getNextSibling() != null) {
1632                     fatal(node, "tRNS node has more than one child!");
1633                 }
1634 
1635                 tRNS_present = true;
1636             } else if (name.equals("zTXt")) {
1637                 Node zTXt_node = node.getFirstChild();
1638                 while (zTXt_node != null) {
1639                     if (!zTXt_node.getNodeName().equals("zTXtEntry")) {
1640                         fatal(node,
1641                               "Only an zTXtEntry may be a child of an zTXt!");
1642                     }
1643 
1644                     String keyword = getAttribute(zTXt_node, "keyword");
1645                     zTXt_keyword.add(keyword);
1646 
1647                     int compressionMethod =
1648                         getEnumeratedAttribute(zTXt_node, "compressionMethod",
1649                                                zTXt_compressionMethodNames);
1650                     zTXt_compressionMethod.add(new Integer(compressionMethod));
1651 
1652                     String text = getAttribute(zTXt_node, "text");
1653                     zTXt_text.add(text);
1654 
1655                     zTXt_node = zTXt_node.getNextSibling();
1656                 }
1657             } else if (name.equals("UnknownChunks")) {
1658                 Node unknown_node = node.getFirstChild();
1659                 while (unknown_node != null) {
1660                     if (!unknown_node.getNodeName().equals("UnknownChunk")) {
1661                         fatal(node,
1662                    "Only an UnknownChunk may be a child of an UnknownChunks!");
1663                     }
1664                     String chunkType = getAttribute(unknown_node, "type");
1665                     Object chunkData =
1666                         ((IIOMetadataNode)unknown_node).getUserObject();
1667 
1668                     if (chunkType.length() != 4) {
1669                         fatal(unknown_node,
1670                               "Chunk type must be 4 characters!");
1671                     }
1672                     if (chunkData == null) {
1673                         fatal(unknown_node,
1674                               "No chunk data present in user object!");
1675                     }
1676                     if (!(chunkData instanceof byte[])) {
1677                         fatal(unknown_node,
1678                               "User object not a byte array!");
1679                     }
1680                     unknownChunkType.add(chunkType);
1681                     unknownChunkData.add(((byte[])chunkData).clone());
1682 
1683                     unknown_node = unknown_node.getNextSibling();
1684                 }
1685             } else {
1686                 fatal(node, "Unknown child of root node!");
1687             }
1688 
1689             node = node.getNextSibling();
1690         }
1691     }
1692 
1693     /*
1694      * Accrding to PNG spec, keywords are restricted to 1 to 79 bytes
1695      * in length. Keywords shall contain only printable Latin-1 characters
1696      * and spaces; To reduce the chances for human misreading of a keyword,
1697      * leading spaces, trailing spaces, and consecutive spaces are not
1698      * permitted in keywords.
1699      *
1700      * See: http://www.w3.org/TR/PNG/#11keywords
1701      */
1702     private boolean isValidKeyword(String s) {
1703         int len = s.length();
1704         if (len < 1 || len >= 80) {
1705             return false;
1706         }
1707         if (s.startsWith(" ") || s.endsWith(" ") || s.contains("  ")) {
1708             return false;
1709         }
1710         return isISOLatin(s, false);
1711     }
1712 
1713     /*
1714      * According to PNG spec, keyword shall contain only printable
1715      * Latin-1 [ISO-8859-1] characters and spaces; that is, only
1716      * character codes 32-126 and 161-255 decimal are allowed.
1717      * For Latin-1 value fields the 0x10 (linefeed) control
1718      * character is aloowed too.
1719      *
1720      * See: http://www.w3.org/TR/PNG/#11keywords
1721      */
1722     private boolean isISOLatin(String s, boolean isLineFeedAllowed) {
1723         int len = s.length();
1724         for (int i = 0; i < len; i++) {
1725             char c = s.charAt(i);
1726             if (c < 32 || c > 255 || (c > 126 && c < 161)) {
1727                 // not printable. Check whether this is an allowed
1728                 // control char
1729                 if (!isLineFeedAllowed || c != 0x10) {
1730                     return false;
1731                 }
1732             }
1733         }
1734         return true;
1735     }
1736 
1737     private void mergeStandardTree(Node root)
1738         throws IIOInvalidTreeException {
1739         Node node = root;
1740         if (!node.getNodeName()
1741             .equals(IIOMetadataFormatImpl.standardMetadataFormatName)) {
1742             fatal(node, "Root must be " +
1743                   IIOMetadataFormatImpl.standardMetadataFormatName);
1744         }
1745 
1746         node = node.getFirstChild();
1747         while (node != null) {
1748             String name = node.getNodeName();
1749 
1750             if (name.equals("Chroma")) {
1751                 Node child = node.getFirstChild();
1752                 while (child != null) {
1753                     String childName = child.getNodeName();
1754                     if (childName.equals("Gamma")) {
1755                         float gamma = getFloatAttribute(child, "value");
1756                         gAMA_present = true;
1757                         gAMA_gamma = (int)(gamma*100000 + 0.5);
1758                     } else if (childName.equals("Palette")) {
1759                         byte[] red = new byte[256];
1760                         byte[] green = new byte[256];
1761                         byte[] blue = new byte[256];
1762                         int maxindex = -1;
1763 
1764                         Node entry = child.getFirstChild();
1765                         while (entry != null) {
1766                             int index = getIntAttribute(entry, "index");
1767                             if (index >= 0 && index <= 255) {
1768                                 red[index] =
1769                                     (byte)getIntAttribute(entry, "red");
1770                                 green[index] =
1771                                     (byte)getIntAttribute(entry, "green");
1772                                 blue[index] =
1773                                     (byte)getIntAttribute(entry, "blue");
1774                                 if (index > maxindex) {
1775                                     maxindex = index;
1776                                 }
1777                             }
1778                             entry = entry.getNextSibling();
1779                         }
1780 
1781                         int numEntries = maxindex + 1;
1782                         PLTE_red = new byte[numEntries];
1783                         PLTE_green = new byte[numEntries];
1784                         PLTE_blue = new byte[numEntries];
1785                         System.arraycopy(red, 0, PLTE_red, 0, numEntries);
1786                         System.arraycopy(green, 0, PLTE_green, 0, numEntries);
1787                         System.arraycopy(blue, 0, PLTE_blue, 0, numEntries);
1788                         PLTE_present = true;
1789                     } else if (childName.equals("BackgroundIndex")) {
1790                         bKGD_present = true;
1791                         bKGD_colorType = PNGImageReader.PNG_COLOR_PALETTE;
1792                         bKGD_index = getIntAttribute(child, "value");
1793                     } else if (childName.equals("BackgroundColor")) {
1794                         int red = getIntAttribute(child, "red");
1795                         int green = getIntAttribute(child, "green");
1796                         int blue = getIntAttribute(child, "blue");
1797                         if (red == green && red == blue) {
1798                             bKGD_colorType = PNGImageReader.PNG_COLOR_GRAY;
1799                             bKGD_gray = red;
1800                         } else {
1801                             bKGD_red = red;
1802                             bKGD_green = green;
1803                             bKGD_blue = blue;
1804                         }
1805                         bKGD_present = true;
1806                     }
1807 //                  } else if (childName.equals("ColorSpaceType")) {
1808 //                  } else if (childName.equals("NumChannels")) {
1809 
1810                     child = child.getNextSibling();
1811                 }
1812             } else if (name.equals("Compression")) {
1813                 Node child = node.getFirstChild();
1814                 while (child != null) {
1815                     String childName = child.getNodeName();
1816                     if (childName.equals("NumProgressiveScans")) {
1817                         // Use Adam7 if NumProgressiveScans > 1
1818                         int scans = getIntAttribute(child, "value");
1819                         IHDR_interlaceMethod = (scans > 1) ? 1 : 0;
1820 //                  } else if (childName.equals("CompressionTypeName")) {
1821 //                  } else if (childName.equals("Lossless")) {
1822 //                  } else if (childName.equals("BitRate")) {
1823                     }
1824                     child = child.getNextSibling();
1825                 }
1826             } else if (name.equals("Data")) {
1827                 Node child = node.getFirstChild();
1828                 while (child != null) {
1829                     String childName = child.getNodeName();
1830                     if (childName.equals("BitsPerSample")) {
1831                         String s = getAttribute(child, "value");
1832                         StringTokenizer t = new StringTokenizer(s);
1833                         int maxBits = -1;
1834                         while (t.hasMoreTokens()) {
1835                             int bits = Integer.parseInt(t.nextToken());
1836                             if (bits > maxBits) {
1837                                 maxBits = bits;
1838                             }
1839                         }
1840                         if (maxBits < 1) {
1841                             maxBits = 1;
1842                         }
1843                         if (maxBits == 3) maxBits = 4;
1844                         if (maxBits > 4 || maxBits < 8) {
1845                             maxBits = 8;
1846                         }
1847                         if (maxBits > 8) {
1848                             maxBits = 16;
1849                         }
1850                         IHDR_bitDepth = maxBits;
1851                     } else if (childName.equals("SignificantBitsPerSample")) {
1852                         String s = getAttribute(child, "value");
1853                         StringTokenizer t = new StringTokenizer(s);
1854                         int numTokens = t.countTokens();
1855                         if (numTokens == 1) {
1856                             sBIT_colorType = PNGImageReader.PNG_COLOR_GRAY;
1857                             sBIT_grayBits = Integer.parseInt(t.nextToken());
1858                         } else if (numTokens == 2) {
1859                             sBIT_colorType =
1860                               PNGImageReader.PNG_COLOR_GRAY_ALPHA;
1861                             sBIT_grayBits = Integer.parseInt(t.nextToken());
1862                             sBIT_alphaBits = Integer.parseInt(t.nextToken());
1863                         } else if (numTokens == 3) {
1864                             sBIT_colorType = PNGImageReader.PNG_COLOR_RGB;
1865                             sBIT_redBits = Integer.parseInt(t.nextToken());
1866                             sBIT_greenBits = Integer.parseInt(t.nextToken());
1867                             sBIT_blueBits = Integer.parseInt(t.nextToken());
1868                         } else if (numTokens == 4) {
1869                             sBIT_colorType =
1870                               PNGImageReader.PNG_COLOR_RGB_ALPHA;
1871                             sBIT_redBits = Integer.parseInt(t.nextToken());
1872                             sBIT_greenBits = Integer.parseInt(t.nextToken());
1873                             sBIT_blueBits = Integer.parseInt(t.nextToken());
1874                             sBIT_alphaBits = Integer.parseInt(t.nextToken());
1875                         }
1876                         if (numTokens >= 1 && numTokens <= 4) {
1877                             sBIT_present = true;
1878                         }
1879 //                      } else if (childName.equals("PlanarConfiguration")) {
1880 //                      } else if (childName.equals("SampleFormat")) {
1881 //                      } else if (childName.equals("SampleMSB")) {
1882                     }
1883                     child = child.getNextSibling();
1884                 }
1885             } else if (name.equals("Dimension")) {
1886                 boolean gotWidth = false;
1887                 boolean gotHeight = false;
1888                 boolean gotAspectRatio = false;
1889 
1890                 float width = -1.0F;
1891                 float height = -1.0F;
1892                 float aspectRatio = -1.0F;
1893 
1894                 Node child = node.getFirstChild();
1895                 while (child != null) {
1896                     String childName = child.getNodeName();
1897                     if (childName.equals("PixelAspectRatio")) {
1898                         aspectRatio = getFloatAttribute(child, "value");
1899                         gotAspectRatio = true;
1900                     } else if (childName.equals("HorizontalPixelSize")) {
1901                         width = getFloatAttribute(child, "value");
1902                         gotWidth = true;
1903                     } else if (childName.equals("VerticalPixelSize")) {
1904                         height = getFloatAttribute(child, "value");
1905                         gotHeight = true;
1906 //                  } else if (childName.equals("ImageOrientation")) {
1907 //                  } else if
1908 //                      (childName.equals("HorizontalPhysicalPixelSpacing")) {
1909 //                  } else if
1910 //                      (childName.equals("VerticalPhysicalPixelSpacing")) {
1911 //                  } else if (childName.equals("HorizontalPosition")) {
1912 //                  } else if (childName.equals("VerticalPosition")) {
1913 //                  } else if (childName.equals("HorizontalPixelOffset")) {
1914 //                  } else if (childName.equals("VerticalPixelOffset")) {
1915                     }
1916                     child = child.getNextSibling();
1917                 }
1918 
1919                 if (gotWidth && gotHeight) {
1920                     pHYs_present = true;
1921                     pHYs_unitSpecifier = 1;
1922                     pHYs_pixelsPerUnitXAxis = (int)(width*1000 + 0.5F);
1923                     pHYs_pixelsPerUnitYAxis = (int)(height*1000 + 0.5F);
1924                 } else if (gotAspectRatio) {
1925                     pHYs_present = true;
1926                     pHYs_unitSpecifier = 0;
1927 
1928                     // Find a reasonable rational approximation
1929                     int denom = 1;
1930                     for (; denom < 100; denom++) {
1931                         int num = (int)(aspectRatio*denom);
1932                         if (Math.abs(num/denom - aspectRatio) < 0.001) {
1933                             break;
1934                         }
1935                     }
1936                     pHYs_pixelsPerUnitXAxis = (int)(aspectRatio*denom);
1937                     pHYs_pixelsPerUnitYAxis = denom;
1938                 }
1939             } else if (name.equals("Document")) {
1940                 Node child = node.getFirstChild();
1941                 while (child != null) {
1942                     String childName = child.getNodeName();
1943                     if (childName.equals("ImageModificationTime")) {
1944                         tIME_present = true;
1945                         tIME_year = getIntAttribute(child, "year");
1946                         tIME_month = getIntAttribute(child, "month");
1947                         tIME_day = getIntAttribute(child, "day");
1948                         tIME_hour =
1949                             getIntAttribute(child, "hour", 0, false);
1950                         tIME_minute =
1951                             getIntAttribute(child, "minute", 0, false);
1952                         tIME_second =
1953                             getIntAttribute(child, "second", 0, false);
1954 //                  } else if (childName.equals("SubimageInterpretation")) {
1955 //                  } else if (childName.equals("ImageCreationTime")) {
1956                     }
1957                     child = child.getNextSibling();
1958                 }
1959             } else if (name.equals("Text")) {
1960                 Node child = node.getFirstChild();
1961                 while (child != null) {
1962                     String childName = child.getNodeName();
1963                     if (childName.equals("TextEntry")) {
1964                         String keyword =
1965                             getAttribute(child, "keyword", "", false);
1966                         String value = getAttribute(child, "value");
1967                         String language =
1968                             getAttribute(child, "language", "", false);
1969                         String compression =
1970                             getAttribute(child, "compression", "none", false);
1971 
1972                         if (!isValidKeyword(keyword)) {
1973                             // Just ignore this node, PNG requires keywords
1974                         } else if (isISOLatin(value, true)) {
1975                             if (compression.equals("zip")) {
1976                                 // Use a zTXt node
1977                                 zTXt_keyword.add(keyword);
1978                                 zTXt_text.add(value);
1979                                 zTXt_compressionMethod.add(Integer.valueOf(0));
1980                             } else {
1981                                 // Use a tEXt node
1982                                 tEXt_keyword.add(keyword);
1983                                 tEXt_text.add(value);
1984                             }
1985                         } else {
1986                             // Use an iTXt node
1987                             iTXt_keyword.add(keyword);
1988                             iTXt_compressionFlag.add(Boolean.valueOf(compression.equals("zip")));
1989                             iTXt_compressionMethod.add(Integer.valueOf(0));
1990                             iTXt_languageTag.add(language);
1991                             iTXt_translatedKeyword.add(keyword); // fake it
1992                             iTXt_text.add(value);
1993                         }
1994                     }
1995                     child = child.getNextSibling();
1996                 }
1997 //          } else if (name.equals("Transparency")) {
1998 //              Node child = node.getFirstChild();
1999 //              while (child != null) {
2000 //                  String childName = child.getNodeName();
2001 //                  if (childName.equals("Alpha")) {
2002 //                  } else if (childName.equals("TransparentIndex")) {
2003 //                  } else if (childName.equals("TransparentColor")) {
2004 //                  } else if (childName.equals("TileTransparencies")) {
2005 //                  } else if (childName.equals("TileOpacities")) {
2006 //                  }
2007 //                  child = child.getNextSibling();
2008 //              }
2009 //          } else {
2010 //              // fatal(node, "Unknown child of root node!");
2011             }
2012 
2013             node = node.getNextSibling();
2014         }
2015     }
2016 
2017     // Reset all instance variables to their initial state
2018     public void reset() {
2019         IHDR_present = false;
2020         PLTE_present = false;
2021         bKGD_present = false;
2022         cHRM_present = false;
2023         gAMA_present = false;
2024         hIST_present = false;
2025         iCCP_present = false;
2026         iTXt_keyword = new ArrayList<String>();
2027         iTXt_compressionFlag = new ArrayList<Boolean>();
2028         iTXt_compressionMethod = new ArrayList<Integer>();
2029         iTXt_languageTag = new ArrayList<String>();
2030         iTXt_translatedKeyword = new ArrayList<String>();
2031         iTXt_text = new ArrayList<String>();
2032         pHYs_present = false;
2033         sBIT_present = false;
2034         sPLT_present = false;
2035         sRGB_present = false;
2036         tEXt_keyword = new ArrayList<String>();
2037         tEXt_text = new ArrayList<String>();
2038         tIME_present = false;
2039         tRNS_present = false;
2040         zTXt_keyword = new ArrayList<String>();
2041         zTXt_compressionMethod = new ArrayList<Integer>();
2042         zTXt_text = new ArrayList<String>();
2043         unknownChunkType = new ArrayList<String>();
2044         unknownChunkData = new ArrayList<byte[]>();
2045     }
2046 }